/* 
 * File:   SOM.cpp
 * Author: Matej Pechac
 * 
 * Created on April 18, 2013, 9:47 PM
 */

#include "SOM.h"
#include <cmath>

SOM::SOM(int input_dim, int map_dim_x, int map_dim_y) {
  	map_layer		= new Neuron*[map_dim_x*map_dim_y];
	input_layer		= new float[input_dim];

	this->map_dim_x = map_dim_x;
	this->map_dim_y = map_dim_y;
	this->input_dim = input_dim;


	for(int i = 0; i < map_dim_y; i++) {
		for(int j = 0; j < map_dim_x; j++) {
			map_layer[i*map_dim_x+j] = new Neuron(input_dim, 0, j, i);
		}
	}

	sigma0 = sqrt((float)map_dim_x);
}

SOM::SOM(const SOM& orig) {
}

SOM::~SOM() {
  delete[] map_layer;
}

void SOM::insert_dataset(CDataSet* data) {
  trn_dataset = data;
}

void SOM::init_training(int epochs, float gama) {
	lambda = (float)epochs / log((float)max(map_dim_x, map_dim_y)/2.0f);
	eta = (float)epochs;
	gama0 = gama;
}

void SOM::train(int epochs, sSOMTrnConfig config, bool loging) {
	list<sSequence>                 *trn_data = trn_dataset->get_trn_data();
	list<sSequence>::iterator       seq_it;
  list< vector<float> >::iterator	sam_it;
	float	qerr;
  
  init_training(epochs, config.gama);
  
  for(int i = 0; i < epochs; i++) {
    bool _entropy = (i % 10 == 0);

    qerr = 0;
    param_decay(i, config.dec_fn);

    for (seq_it=trn_data->begin(); seq_it != trn_data->end(); seq_it++) {
      sSequence sample = *seq_it;

      for (sam_it = sample.seq.begin(); sam_it != sample.seq.end(); sam_it++) {
        qerr += train_one_sample(*sam_it, GAUSSIAN, _entropy);
      }
    }
    cout << i << ". Qerr = " << qerr << endl;
  }
}

void SOM::activate(vector<float> sample, vector<float>* output, int mode) {
	Neuron*		w = NULL;
  float     d;

	input_layer = &sample[0];

	w = find_winner(false);
  
  output->clear();

	for(int i = 0; i < map_dim_x*map_dim_y; i++) {
		d = map_layer[i]->distance(input_layer);
    if (mode == LIN) output->push_back(d);
    if (mode == EXP) output->push_back(exp(-d));
	}  
}

void SOM::get_activation(vector<float> *output, int mode) {
  double val = 0.0;
  double dist = 0.0;
  
  output->clear();
  
  for (int i = 0; i < map_dim_y; i++) {
    for (int j = 0; j < map_dim_x; j++) {
        dist = map_layer[i*map_dim_x+j]->distance(input_layer);

        if (mode == LIN) {
          val = dist;
        }
        if (mode == EXP) {
          val = exp(-dist);
        }
        output->push_back(val);
    }
  }  
}

void SOM::get_size(int* size) {
  size[0] = map_dim_x;
  size[1] = map_dim_y;
}


float SOM::train_one_sample(vector<float> sample, int nf, bool entropy) {
	Neuron*		w = NULL;

	input_layer = &sample[0];

	w = find_winner(entropy);

	float qerr = w->distance(input_layer);

	for(int i = 0; i < map_dim_x*map_dim_y; i++) {
		map_layer[i]->update_weights(gama, w, input_layer, sigma, nf);
	}

	return qerr;
}

Neuron* SOM::find_winner(bool entropy) {
	int		mini = 0;
	float	mind = -1;

	for (int i = 1; i < map_dim_x*map_dim_y; i++) {
		float i_d = map_layer[i]->distance(input_layer);
		if (mind > i_d || mind == -1) {
			mini = i;
			mind = i_d;
		}
	}

	return map_layer[mini];
}

void SOM::param_decay(int epoch, int mode) {
  sigma_decay(epoch);
  gama_decay(epoch, mode);
}

void SOM::sigma_decay(int t) {
	sigma = sigma0 * exp(-(float)t / lambda);
}

void SOM::gama_decay(int t, int mode) {
  if (mode == EXP) {
    gama = gama0 * exp(-(float)t / eta);
  }
  if (mode == LIN) {
    gama = gama0 * ((float)(eta-t) / eta);
  }
}

void SOM::write_output(ofstream& myfile, vector<float> *activation) {

  for (int i = 0; i < map_dim_y*map_dim_x; i++) {
      myfile << activation->at(i) << " ";
  }
  myfile << endl;
}

